package preprocessor.absorption;


import kb.*;
import java.util.HashSet;

import entities.Axiom;
import entities.InclusionAxiom;
import entities.concept.Concept;
import entities.concept.NotConcept;
import entities.concept.OrConcept;
import entities.concept.PConcept;
import entities.vocabulary.KBVocabulary;

import utils.exception.ConceptException;
import preprocessor.Simplification;

/**
 * Concept absorption needs to consider negated absorption and enhanced absorption as well.
 * In addition, we impose that concept absorption has higher priority than binary absorption in the
 * sense that given A=>.., B=>..., binary absorption has to consider if it is possible to absorb (A and B) => X
 * This is the same as Enhanced concept absorption problem, i.e., not (A and B)=>Y enforces the addition of 
 * Top => X or Y.
 * 
 * This is now not a constrain on concept absorption, rather one on binary absorption. i.e., binary absorption is given lower 
 * priority than concept absorption.
 * @see    Absorption
 * 
 * @author j55wu
 *
 */
public class ConceptAbsorption implements Absorption{
	
	TBox tbox;
	
	
	public ConceptAbsorption(TBox tb){
		this.tbox = tb;
	}
	
	public TBox returnTBox(){
		return this.tbox;
	}
	
	/**
	 * Given a set of general axioms, with references to Tu, Tnu, Tb and other possible sub-Tboxes,
	 *  the absorption must be able to classify GCIs into appropriate sub-Tboxes.
	 *  
	 *  This method absorbs axioms in Tg.
	 */
	public boolean absorb(){
		//
		boolean suc = true;
		if(this.tbox.getTg().size()>0){
			// try to absorb each GCI
			HashSet<Concept> cs = new HashSet<Concept>();
			for(Concept c:this.tbox.getTg().keySet()){
				boolean abs =absorbAxiom(c, this.tbox.getTg().get(c));
				if(abs){
					//remove this gci
					cs.add(c);
				}
				suc = suc && abs;
			}
			for(Concept cc:cs){
				this.tbox.getTg().remove(cc);
			}
		}
		return suc;
	}
	
	public boolean absorbAxiom(Concept l, Concept r){
		boolean suc = false;
		if(l == null){
			return suc;
		}
		//do not absorb it if LHS is already a named concept.
		/*if(l instanceof PConcept){
			try{
			this.tbox.insertTu((PConcept)l, r);
			}catch(Exception e){
				e.printStackTrace();
			}
		}else{*/
			//get disjuncts
			HashSet<Concept> cons = new HashSet<Concept>();
			cons.add(r);
			cons.add(Simplification.NNF(new NotConcept(l),false));
			suc = this.applyConceptAbsorption(Concept.reduceToDisjuncts(new OrConcept(cons)));			
		//}		
		return suc;
	}
	
	public boolean absorbAxiom(InclusionAxiom ax){
		boolean suc = false;
		if(ax == null){
			return suc;
		}
		//do not absorb it if LHS is already a named concept.
		if(ax.getLHS() instanceof PConcept){
			try{
			this.tbox.insertTu(ax);
			}catch(Exception e){
				e.printStackTrace();
			}
		}else{
			//get disjuncts
			suc = this.applyConceptAbsorption(Axiom.axiomToDisjuncts(ax));
			//make decisions about how to absorb here			
		}		
		return suc;
	}
	
	private boolean applyConceptAbsorption(HashSet<Concept> dis){
		boolean suc = false;
		for(Concept c:dis){
			if(c instanceof NotConcept){
				NotConcept nc = (NotConcept) c;
				if(!(nc.getConcept() instanceof PConcept)){
					continue;
				}
				PConcept pc = (PConcept) nc.getConcept();
				//decide if Negated concepts matters!
				if(Absorption.NCA && tbox.getTnu().containsKey(nc)){
					//consider if enhanced concept absorption allowed
					//~A=>C1 A=>C2, then Top=>C1 or C2
					if(Absorption.ECA){
						//absorb and add an extra GCI...
						Concept c1 = tbox.getTnu().get(nc);
						//a new set of remaining items
						HashSet<Concept> disnew = new HashSet<Concept>();
						disnew.addAll(dis);
						disnew.remove(c);
						//absorb
						try{
							tbox.insertTu(pc, new OrConcept(disnew));
						}catch(ConceptException e){
							e.printStackTrace();
						}
						//add an extra GCI
						disnew.add(c1);
						Concept c2 = new OrConcept(disnew);
						tbox.saveGeneratedGCIs(KBVocabulary.CONCEPT_TOP, c2);
						suc = true;//because new GCI generated
						break;
					}else{
						continue;
					}
				}else{
					//there is no competition considered: first come first serve~~
					//so the order of concepts in a set really matters~ and absorption may give different results.
					HashSet<Concept> disnew = new HashSet<Concept>();
					disnew.addAll(dis);
					disnew.remove(c);
					try{
						if(disnew.size() ==1){
							tbox.insertTu(pc, disnew.iterator().next());
						}else{
						tbox.insertTu(pc, new OrConcept(disnew));
						}
						suc = true;
						break;
					}catch(ConceptException e){
						e.printStackTrace();
					}
				}

			}else if(Absorption.NCA && c instanceof PConcept){
				//negated concept absorption
				NotConcept nc = new NotConcept(c);
				if(tbox.getTu().containsKey(c)){
					//consider if enhanced concept absorption allowed
					//A=>C1 ~A=>C2, then Top=>C1 or C2
					if(Absorption.ECA){
						//absorb and add an extra GCI...
						Concept c1 = tbox.getTu().get(c);
						//a new set of remaining items
						HashSet<Concept> disnew = new HashSet<Concept>();
						disnew.addAll(dis);
						disnew.remove(c);
						//absorb
						try{
							tbox.insertTnu(nc, new OrConcept(disnew));
						}catch(ConceptException e){
							e.printStackTrace();
						}
						//add an extra GCI
						disnew.add(c1);
						Concept c2 = new OrConcept(disnew);
						tbox.saveGeneratedGCIs(KBVocabulary.CONCEPT_TOP, c2);
						suc = true;//because new GCI generated
						break;
					}else{
						continue;
					}
				}else{
					//~A -> C added directly, no more GCIs added
					HashSet<Concept> disnew = new HashSet<Concept>();
					disnew.addAll(dis);
					disnew.remove(c);
					try{
						if(disnew.size()==1){
							tbox.insertTnu(nc, disnew.iterator().next());
							/*System.out.println("NC absoroption: "+utils.Renderer.renderConcept(nc)
									+"->"+utils.Renderer.renderConcept(disnew.iterator().next()));*/
						}else{
							tbox.insertTnu(nc, new OrConcept(disnew));
						}
						suc = true;
						break;
					}catch(ConceptException e){
						e.printStackTrace();
					}
				}
				
			}
		//no more cases considered in this absorption
		}
		//end of for loop
		return suc;
	}

	
	
}
